# -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import logging
import time

from openerp import tools
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.tools import float_compare, DEFAULT_SERVER_DATETIME_FORMAT
import openerp.addons.decimal_precision as dp
import openerp.addons.product.product

_logger = logging.getLogger(__name__)

class pos_order(osv.osv):
    _inherit = "pos.order"

    def create_from_ui(self, cr, uid, orders, context=None):
        # Keep only new orders
        submitted_references = [o['data']['name'] for o in orders]
        existing_order_ids = self.search(cr, uid, [('pos_reference', 'in', submitted_references)], context=context)
        existing_orders = self.read(cr, uid, existing_order_ids, ['pos_reference'], context=context)
        existing_references = set([o['pos_reference'] for o in existing_orders])
        orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
        order_ids = []

        for tmp_order in orders_to_save:
            to_invoice = tmp_order['to_invoice']
            order = tmp_order['data']
            order_id = self.create(cr, uid, self._order_fields(cr, uid, order, context=context),context)

            for payments in order['statement_ids']:
                amount_st = payments[2]['amount'] or 0.0
                self.add_payment(cr, uid, order_id, self._payment_fields(cr, uid, payments[2], context=context), context=context)

            session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context)
            if session.sequence_number <= order['sequence_number']:
                session.write({'sequence_number': order['sequence_number'] + 1})
                session.refresh()
            
            if order['amount_return']:
                cash_journal = session.cash_journal_id
                if not cash_journal:
                    cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', session.statement_ids)
                    if not len(cash_journal_ids):
                        raise osv.except_osv( _('error!'),
                            _("No cash statement found for this session. Unable to record returned cash."))
                    cash_journal = cash_journal_ids[0].journal_id
                self.add_payment(cr, uid, order_id, {
                    'amount': -order['amount_return'],
                    'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'),
                    'payment_name': _('return'),
                    'journal': cash_journal.id,
                }, context=context)
            order_ids.append(order_id)

            try:
                self.signal_workflow(cr, uid, [order_id], 'paid')
            except Exception as e:
                _logger.error('Could not fully process the POS Order: %s', tools.ustr(e))

            if to_invoice:
                cash_journal = session.cash_journal_id
                if not cash_journal:
                    cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', session.statement_ids)
                    if not len(cash_journal_ids):
                        raise osv.except_osv( _('error!'),
                            _("No cash statement found for this session. Unable to record returned cash."))
                    cash_journal = cash_journal_ids[0].journal_id
                self.add_payment(cr, uid, order_id, {
                    'amount': -(amount_st - order['amount_return']),
                    'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'),
                    'payment_name': _('invoice'),
                    'journal': cash_journal.id,
                }, context=context)
                self.action_invoice(cr, uid, [order_id], context)
                order_obj = self.browse(cr, uid, order_id, context)
                self.pool['account.invoice'].signal_workflow(cr, uid, [order_obj.invoice_id.id], 'invoice_open')

        return order_ids

    def action_invoice2(self, cr, uid, ids, context=None):
        am_obj = self.pool.get('account.move')
        aml_obj = self.pool.get('account.move.line')
        abs_obj = self.pool.get('account.bank.statement')
        property_obj = self.pool.get('ir.property')
        for order in self.browse(cr, uid, ids, context=context):
            cash_journal = order.session_id.cash_journal_id
            if not cash_journal:
                cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', order.session_id.statement_ids)
                if not len(cash_journal_ids):
                    raise osv.except_osv( _('error!'),
                        _("No cash statement found for this session. Unable to record returned cash."))
                cash_journal = cash_journal_ids[0].journal_id
            self.add_payment(cr, uid, order.id, {
                    'amount': -order.amount_total,
                    'payment_date': order.date_order,
                    'payment_name': _('invoice'),
                    'journal': cash_journal.id,
                }, context=None)
            self.action_invoice(cr, uid, [order.id], context)
        return True

    def action_invoice(self, cr, uid, ids, context=None):
        inv_ref = self.pool.get('account.invoice')
        inv_line_ref = self.pool.get('account.invoice.line')
        product_obj = self.pool.get('product.product')
        abs_obj = self.pool.get('account.bank.statement')
        inv_ids = []
        
        for order in self.pool.get('pos.order').browse(cr, uid, ids, context=context):
            if order.invoice_id:
                inv_ids.append(order.invoice_id.id)
                continue

            if not order.partner_id:
                raise osv.except_osv(_('Error!'), _('Please provide a partner for the sale.'))

            acc = order.partner_id.property_account_receivable.id
            inv = {
                'date_invoice': order.date_order[:10],
                'name': order.name,
                'origin': order.name,
                'account_id': acc,
                'journal_id': order.sale_journal.id or None,
                'type': 'out_invoice',
                'reference': order.name,
                'partner_id': order.partner_id.id,
                'comment': order.note or '',
                'currency_id': order.pricelist_id.currency_id.id, # considering partner's sale pricelist's currency
            }
            inv.update(inv_ref.onchange_partner_id(cr, uid, [], 'out_invoice', order.partner_id.id)['value'])
            if not inv.get('account_id', None):
                inv['account_id'] = acc
            inv_id = inv_ref.create(cr, uid, inv, context=context)

            self.write(cr, uid, [order.id], {'invoice_id': inv_id, 'state': 'invoiced'}, context=context)
            inv_ids.append(inv_id)
            for line in order.lines:
                inv_line = {
                    'invoice_id': inv_id,
                    'product_id': line.product_id.id,
                    'quantity': line.qty,
                }
                inv_name = product_obj.name_get(cr, uid, [line.product_id.id], context=context)[0][1]
                inv_line.update(inv_line_ref.product_id_change(cr, uid, [],
                                                               line.product_id.id,
                                                               line.product_id.uom_id.id,
                                                               line.qty, partner_id = order.partner_id.id,
                                                               fposition_id=order.partner_id.property_account_position.id)['value'])
                inv_line['price_unit'] = line.price_unit
                inv_line['discount'] = line.discount
                inv_line['name'] = inv_name
                inv_line['invoice_line_tax_id'] = [(6, 0, [x.id for x in line.product_id.taxes_id] )]
                inv_line_ref.create(cr, uid, inv_line, context=context)
#             cash_journal = order.session_id.cash_journal_id
#             if not cash_journal:
#                 cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', order.session_id.statement_ids)
#                 if not len(cash_journal_ids):
#                     raise osv.except_osv( _('error!'),
#                         _("No cash statement found for this session. Unable to record returned cash."))
#                 cash_journal = cash_journal_ids[0].journal_id
#             statement_id = False
#             for st in order.session_id.statement_ids:
#                 statement_id = st
#             statement = abs_obj.browse(cr, uid, statement_id, context=context)
#             abs_obj.write(cr, uid, [statement_id], {'balance_end_real': (statement.balance_end_real - order.amount_total)}, context=None)
#             self.add_payment(cr, uid, order.id, {
#                     'amount': -order.amount_total,
#                     'payment_date': order.date_order,
#                     'payment_name': _('invoice'),
#                     'journal': cash_journal.id,
#                 }, context=None)
            inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context)
            self.signal_workflow(cr, uid, [order.id], 'invoice')
            inv_ref.signal_workflow(cr, uid, [inv_id], 'validate')

        if not inv_ids: return {}

        mod_obj = self.pool.get('ir.model.data')
        res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
        res_id = res and res[1] or False
        return {
            'name': _('Customer Invoice'),
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': [res_id],
            'res_model': 'account.invoice',
            'context': "{'type':'out_invoice'}",
            'type': 'ir.actions.act_window',
            'nodestroy': True,
            'target': 'current',
            'res_id': inv_ids and inv_ids[0] or False,
        }

    def action_create_invoice(self, cr, uid, ids, context=None):
        am_obj = self.pool.get('account.move')
        aml_obj = self.pool.get('account.move.line')
        abs_obj = self.pool.get('account.bank.statement')
        property_obj = self.pool.get('ir.property')
        for order in self.browse(cr, uid, ids, context=context):
            self.write(cr, uid, ids, {'state':'draft','invoice_id':False})
            self.delete_workflow(cr, uid, [order.id])
            self.create_workflow(cr, uid, [order.id])
            self.signal_workflow(cr, uid, [order.id], 'invoice')
            self.action_invoice(cr, uid, [order.id], context)
            self.pool['account.invoice'].signal_workflow(cr, uid, [order.invoice_id.id], 'invoice_open')
            
            if order.session_id.state == 'closed':
                statement_id = False
                for st in order.session_id.statement_ids:
                    statement_id = st

                cash_journal = order.session_id.cash_journal_id
                if not cash_journal:
                    cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', order.session_id.statement_ids)
                    if not len(cash_journal_ids):
                        raise osv.except_osv( _('error!'),
                            _("No cash statement found for this session. Unable to record returned cash."))
                    cash_journal = cash_journal_ids[0].journal_id
                statement_id = False
                for st in order.session_id.statement_ids:
                    statement_id = st

                statement_id.write({'balance_end_real': (statement_id.balance_end_real - order.amount_total)})
#                 abs_obj.write(cr, uid, [statement_id], {'balance_end_real': (statement.balance_end_real - order.amount_total)}, context=None)
                self.add_payment(cr, uid, order.id, {
                        'amount': -order.amount_total,
                        'payment_date': order.date_order,
                        'payment_name': _('invoice'),
                        'journal': cash_journal.id,
                    }, context=None)


                move_name = (statement_id.name or order.session_id.name) + "/" + str('cancel_invoice')
                move_vals = {
                    'journal_id': statement_id.journal_id.id,
                    'period_id': statement_id.period_id.id,
                    'date': order.date_order[:10],
                    'name': move_name,
                    'ref': order.session_id.name,
                    }
                move_id = am_obj.create(cr, uid, move_vals, context=context)
                account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context)
                current_company = order.sale_journal.company_id
                debit_account = order.partner_id and \
                    order.partner_id.property_account_receivable and \
                    order.partner_id.property_account_receivable.id or \
                    account_def and account_def.id or current_company.account_receivable.id
                credit_account = statement_id.journal_id and \
                    statement_id.journal_id.default_debit_account_id and \
                    statement_id.journal_id.default_debit_account_id.id
                credit_move_vals = { 'name': order.name + ': cancel invoice',
                                    'date': order.date_order[:10],
                                    'ref': order.session_id.name,
                                    'move_id': move_id,
                                    'partner_id': (((order.partner_id) and order.partner_id.id) or False),
                                    'account_id': credit_account,
                                    'credit': order.amount_total,
                                    'debit': 0.00,
                                    'statement_id': statement_id.id,
                                    'journal_id': statement_id.journal_id.id,
                                    'period_id': statement_id.period_id.id,
                                    'currency_id': False,
                                    'amount_currency': False,
                                        }
                debit_move_vals = { 'name': order.name + ': cancel invoice',
                                    'date': order.date_order[:10],
                                    'ref': order.session_id.name,
                                    'move_id': move_id,
                                    'partner_id': (((order.partner_id) and order.partner_id.id) or False),
                                    'account_id': debit_account,
                                    'credit': 0.00,
                                    'debit': order.amount_total,
                                    'statement_id': statement_id.id,
                                    'journal_id': statement_id.journal_id.id,
                                    'period_id': statement_id.period_id.id,
                                    'currency_id': False,
                                    'amount_currency': False,
                                        }
                aml_obj.create(cr, uid, credit_move_vals, context=context)
                aml_obj.create(cr, uid, debit_move_vals, context=context)
        return True

    def action_undo_invoice(self, cr, uid, ids, context=None):
        am_obj = self.pool.get('account.move')
        aml_obj = self.pool.get('account.move.line')
        property_obj = self.pool.get('ir.property')
        for order in self.browse(cr, uid, ids, context=context):
            if order.invoice_id:
                if order.invoice_id.state != 'cancel':
                    raise osv.except_osv(_('Error!'), _('you need to cancel or delete the related invoices ("%s").' % (order.invoice_id.number,)))

            cash_journal = order.session_id.cash_journal_id
            if not cash_journal:
                cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', order.session_id.statement_ids)
                if not len(cash_journal_ids):
                    raise osv.except_osv( _('error!'),
                        _("No cash statement found for this session. Unable to record returned cash."))
                cash_journal = cash_journal_ids[0].journal_id

            self.add_payment(cr, uid, order.id, {
                    'amount': order.amount_total,
                    'payment_date': order.date_order or time.strftime('%Y-%m-%d %H:%M:%S'),
                    'payment_name': _('cancel_invoice'),
                    'journal': cash_journal.id,
                }, context=context)
            self.write(cr, uid, ids, {'state':'draft','invoice_id':False})
            self.delete_workflow(cr, uid, [order.id])
            self.create_workflow(cr, uid, [order.id])
            self.signal_workflow(cr, uid, [order.id], 'paid')
            
            if order.session_id.state == 'closed':
                statement_id = False
                for st in order.session_id.statement_ids:
                    statement_id = st

                statement_id.write({'balance_end_real': (statement_id.balance_end_real + order.amount_total)})

                move_name = (statement_id.name or order.session_id.name) + "/" + str('cancel_invoice')
                move_vals = {
                    'journal_id': statement_id.journal_id.id,
                    'period_id': statement_id.period_id.id,
                    'date': order.date_order[:10],
                    'name': move_name,
                    'ref': order.session_id.name,
                    }
                move_id = am_obj.create(cr, uid, move_vals, context=context)
                account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context)
                current_company = order.sale_journal.company_id
                credit_account = order.partner_id and \
                    order.partner_id.property_account_receivable and \
                    order.partner_id.property_account_receivable.id or \
                    account_def and account_def.id or current_company.account_receivable.id
                debit_account = statement_id.journal_id and \
                    statement_id.journal_id.default_debit_account_id and \
                    statement_id.journal_id.default_debit_account_id.id
                credit_move_vals = { 'name': order.name + ': cancel invoice',
                                    'date': order.date_order[:10],
                                    'ref': order.session_id.name,
                                    'move_id': move_id,
                                    'partner_id': (((order.partner_id) and order.partner_id.id) or False),
                                    'account_id': credit_account,
                                    'credit': order.amount_total,
                                    'debit': 0.00,
                                    'statement_id': statement_id.id,
                                    'journal_id': statement_id.journal_id.id,
                                    'period_id': statement_id.period_id.id,
                                    'currency_id': False,
                                    'amount_currency': False,
                                        }
                debit_move_vals = { 'name': order.name + ': cancel invoice',
                                    'date': order.date_order[:10],
                                    'ref': order.session_id.name,
                                    'move_id': move_id,
                                    'partner_id': (((order.partner_id) and order.partner_id.id) or False),
                                    'account_id': debit_account,
                                    'credit': 0.00,
                                    'debit': order.amount_total,
                                    'statement_id': statement_id.id,
                                    'journal_id': statement_id.journal_id.id,
                                    'period_id': statement_id.period_id.id,
                                    'currency_id': False,
                                    'amount_currency': False,
                                        }
                aml_obj.create(cr, uid, credit_move_vals, context=context)
                aml_obj.create(cr, uid, debit_move_vals, context=context)
                self.signal_workflow(cr, uid, [order.id], 'done')
        return True


    def create_picking(self, cr, uid, ids, context=None):
        """Create a picking for each order and validate it."""
        picking_obj = self.pool.get('stock.picking')
        partner_obj = self.pool.get('res.partner')
        move_obj = self.pool.get('stock.move')
        product_obj = self.pool.get('product.product')
        ctx = context.copy()
        for order in self.browse(cr, uid, ids, context=context):
            if order.picking_id:
                continue
            product_avail, price_unit_avail = {}, {}
            addr = order.partner_id and partner_obj.address_get(cr, uid, [order.partner_id.id], ['delivery']) or {}
            picking_type = order.picking_type_id
            picking_id = False
            date_qry = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
            
            if picking_type:
                picking_id = picking_obj.create(cr, uid, {
                    'allow_create': True,
                    'origin': order.name,
                    'partner_id': addr.get('delivery',False),
                    'picking_type_id': picking_type.id,
                    'company_id': order.company_id.id,
                    'move_type': 'direct',
                    'note': order.note or "",
                    'invoice_state': 'none',
                    'date_done': date_qry,
                }, context=context)
                self.write(cr, uid, [order.id], {'picking_id': picking_id}, context=context)
            location_id = order.location_id.id
            if order.partner_id:
                destination_id = order.partner_id.property_stock_customer.id
            elif picking_type:
                if not picking_type.default_location_dest_id:
                    raise osv.except_osv(_('Error!'), _('Missing source or destination location for picking type %s. Please configure those fields and try again.' % (picking_type.name,)))
                destination_id = picking_type.default_location_dest_id.id
            else:
                destination_id = partner_obj.default_get(cr, uid, ['property_stock_customer'], context=context)['property_stock_customer']

            move_list = []
            
            for line in order.lines:
                if line.product_id and line.product_id.type == 'service':
                    continue

                move_id  = move_obj.create(cr, uid, {
                    'name': line.name,
                    'product_uom': line.product_id.uom_id.id,
                    'product_uos': line.product_id.uom_id.id,
                    'picking_id': picking_id,
                    'picking_type_id': picking_type.id, 
                    'product_id': line.product_id.id,
                    'product_uos_qty': abs(line.qty),
                    'product_uom_qty': abs(line.qty),
                    'state': 'draft',
                    'location_id': location_id if line.qty >= 0 else destination_id,
                    'location_dest_id': destination_id if line.qty >= 0 else location_id,
                }, context=context)

                move_list.append(move_id)
                todo_move = move_obj.browse(cr, uid, move_id, context=None)
                product = todo_move.product_id
                move_currency_id = todo_move.company_id.currency_id.id
                ctx['currency_id'] = move_currency_id
                qty = todo_move.product_qty
                
                if product.id not in product_avail:
                    # keep track of stock on hand including processed lines not yet marked as done
                    data_found = False
                    cr.execute('''select
                        sm.last_product_qty as last_product,
                        sm.last_cost_price as last_cost,
                        sm.date as sm_date,
                        sm.id as sm_id
                        from
                            stock_move sm 
                        left join 
                            stock_location sl on sm.location_id =sl.id
                        left join
                            stock_location sld on sm.location_dest_id = sld.id
                        where
                            sm.state = 'done' and sm.date <= %s and sm.company_id = %s and sl.usage <> sld.usage and sm.product_id = %s and sm.id != %s
                        order by 
                            sm.date desc, sm.id desc''',(date_qry,todo_move.company_id.id,product.id,todo_move.id))

                    data_crs = cr.dictfetchall()
                    ls_cost = 0.00
                    ls_pr = 0.00
                    if data_crs:
                        for data_cr in data_crs:
                            sm_move_id = data_cr['sm_id']
                            sm_date = data_cr['sm_date']
                            if sm_date == date_qry:
                                if sm_move_id > todo_move.id:
                                    continue
                            if data_found:
                                continue
                            data_found = True
                            ls_cost = (data_cr and 'last_cost' in data_cr and data_cr['last_cost']) or 0.00
                            ls_pr = (data_cr and  'last_product' in data_cr and data_cr['last_product']) or 0.00
                    price_unit_avail[product.id] = ls_cost
                    product_avail[product.id] = ls_pr
                
                if qty > 0:
                    product_avail[product.id] -= qty
                    if product_avail[product.id] < 0:
                        price_unit_avail[product.id] = 0.00
                    move_obj.write(cr, uid, [todo_move.id],
                            {'price_unit': price_unit_avail[product.id],
                             'last_product_qty': product_avail[product.id],
                             'last_cost_price': price_unit_avail[product.id]
                             })

                    last_cost = move_obj._update_the_less_move(cr, uid, product, price_unit_avail[product.id], product_avail[product.id], date_qry, todo_move.company_id.id, move_currency_id, todo_move)

                    product_obj.write(cr, uid, [product.id], {'standard_price': last_cost})

                
            if picking_id:
                picking_obj.action_confirm(cr, uid, [picking_id], context=context)
                picking_obj.force_assign(cr, uid, [picking_id], context=context)
                picking_obj.action_done(cr, uid, [picking_id], context=context)
            elif move_list:
                move_obj.action_confirm(cr, uid, move_list, context=context)
                move_obj.force_assign(cr, uid, move_list, context=context)
                move_obj.action_done(cr, uid, move_list, context=context)

#             print picking_id
#         raise osv.except_osv(_('Error!'), _('Applico 2222'))
        return True

    def cancel_order2(self, cr, uid, ids, context=None):
        """ Changes order state to cancel
        @return: True
        """
        stock_picking_obj = self.pool.get('stock.picking')
        for order in self.browse(cr, uid, ids, context=context):
            stock_picking_obj.action_cancel2(cr, uid, [order.picking_id.id])
            if stock_picking_obj.browse(cr, uid, order.picking_id.id, context=context).state <> 'cancel':
                raise osv.except_osv(_('Error!'), _('Unable to cancel the picking.'))
            
            cash_journal = order.session_id.cash_journal_id
            if not cash_journal:
                cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', order.session_id.statement_ids)
                if not len(cash_journal_ids):
                    raise osv.except_osv( _('error!'),
                        _("No cash statement found for this session. Unable to record returned cash."))
                cash_journal = cash_journal_ids[0].journal_id
            self.add_payment(cr, uid, order.id, {
                'amount': -(order.amount_total),
                'payment_date': order.date_order or time.strftime('%Y-%m-%d %H:%M:%S'),
                'payment_name': _('cancel order'),
                'journal': cash_journal.id,
            }, context=context)
        self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
        return True

pos_order()

class pos_session(osv.osv):
    _inherit = "pos.session"

    def _confirm_orders(self, cr, uid, ids, context=None):
        account_move_obj = self.pool.get('account.move')
        pos_order_obj = self.pool.get('pos.order')
        for session in self.browse(cr, uid, ids, context=context):
            local_context = dict(context or {}, force_company=session.config_id.journal_id.company_id.id)
            order_ids = [order.id for order in session.order_ids if order.state == 'paid']

            move_id = account_move_obj.create(cr, uid, {'ref' : session.name, 'journal_id' : session.config_id.journal_id.id, }, context=local_context)

            pos_order_obj._create_account_move_line(cr, uid, order_ids, session, move_id, context=local_context)

            for order in session.order_ids:
                if order.state == 'done':
                    continue
                if order.state == 'cancel':
                    continue
                if order.state not in ('paid', 'invoiced'):
                    raise osv.except_osv(
                        _('Error!'),
                        _("You cannot confirm all orders of this session, because they have not the 'paid' status"))
                else:
                    pos_order_obj.signal_workflow(cr, uid, [order.id], 'done')

        return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
